Mons, 2019-03-21
Fabrice Flore-Thebault
User & contributor in Molecule / Ansible ecosystem.
Free Software Infrastructure Automation.
Culture, Automation, Measurement, Sharing.
Day 2 routines: system patches, audit, inventory.
Reproductible provisioning, from hypervisor to apps.
Automated backup & restore data.
Maintain environments on shared hosting platforms.
Deploy software.
Build CI pipelines.
Manage everything API: network, cloud, kubernetes.
Validate roles and playbooks before production
Instantiate temporary infrastructure

| Ansible is NOT a programing language |
Molecule has many friends in the toolbox.
Ansible ecosystem
Platforms backends
Dependency backends
Verifiers
Ansible is an IT automation tool. It can configure systems, deploy software, and orchestrate more advanced IT tasks such as continuous deployments or zero downtime rolling updates. Ansible’s main goals are simplicity and ease-of-use.
Molecule is designed to aid in the development and testing of Ansible roles. […] Molecule is opinionated in order to encourage an approach that results in consistently developed roles that are well-written, easily understood and maintained.
Improve the roles quality.
Kill opinion wars.
Ansible Lint is a commandline tool for linting playbooks. Use it to detect behaviors and practices that could potentially be improved.
(local) Virtualization
Cloud provider
Bake your own
Docker
LXC
LXD
Vagrant
Azure
EC2
GCE
Linode
Openstack
| Slow! Keep it for specific cloud features, Windows. |
Delegated
Audit the state of the tested platform after role execution with an independant tool.
Testinfra
Goss
Inspec
Default verifier.
Write tests in python.
Public == python developers.
With Testinfra you can write unit tests in Python to test actual state of your servers configured by management tools.
Easy. YAML syntax, fit well in the Ansible ecosystem.
Fast. Near instantaneous.
Small. <10MB single self-contained binary.
Linux only.
Goss is a YAML based serverspec alternative tool for validating a server’s configuration.
Complex, ruby based, with a feature full DSL.
Linux, MacOS and Windows support.
Public == ruby developers.
InSpec is compliance as code. Turn your compliance, security, and other policy requirements into automated tests.
Usage: molecule [OPTIONS] COMMAND [ARGS]...
_____ _ _
| |___| |___ ___ _ _| |___
| | | | . | | -_| _| | | | -_|
|_|_|_|___|_|___|___|___|_|___|
Molecule aids in the development and testing of Ansible roles.
Enable autocomplete issue:
eval "$(_MOLECULE_COMPLETE=source molecule)"
Options:
--debug / --no-debug Enable or disable debug mode. Default is disabled.
-c, --base-config TEXT Path to a base config. If provided Molecule will
load this config first, and deep merge each
scenario's molecule.yml on top.
(/home/fab/.config/molecule/config.yml)
-e, --env-file TEXT The file to read variables from when rendering
molecule.yml. (.env.yml)
--version Show the version and exit.
--help Show this message and exit.
Commands:
check Use the provisioner to perform a Dry-Run...
cleanup Use the provisioner to cleanup any changes...
converge Use the provisioner to configure instances...
create Use the provisioner to start the instances.
dependency Manage the role's dependencies.
destroy Use the provisioner to destroy the instances.
idempotence Use the provisioner to configure the...
init Initialize a new role or scenario.
lint Lint the role.
list Lists status of instances.
login Log in to one instance.
matrix List matrix of steps used to test instances.
prepare Use the provisioner to prepare the instances...
side-effect Use the provisioner to perform side-effects...
syntax Use the provisioner to syntax check the role.
test Test (lint, destroy, dependency, syntax,...
verify Run automated tests against instances.$ molecule matrix -s default test
--> Test matrix
└── default
├── lint
├── cleanup
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
├── cleanup
└── destroyEnforce syntax rules (ansible-lint, yamllint). |
| Kill opinion wars and improve the roles quality. |
--> Executing Ansible Lint on molecule/default/playbook.yml...
[701] No 'galaxy_info' found
meta/main.yml:1
[306] Shells that use pipes should set the pipefail option
molecule/default/playbook.yml:20
Task/Handler: shell | get version of common_linux
[206] Variables should have spaces before and after: {{ var_name }}
tasks/task_60_cron.yml:19
path: "/etc/cron.{{cron_item}}"To be used in conjunction with prepare |
| Cleanup changes that were made outside of Molecule’s test platforms. |
| remote database connections |
| user accounts |
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.| Destroy the temporary platforms. |
molecule destroy
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
changed: [localhost] => (item={'started': 1, 'finished': 0, 'ansible_job_id': '498157453262.13184', 'results_file': '/home/fab/.ansible_async/498157453262.13184', '_ansible_parsed': True, 'changed': True, '_ansible_no_log': False, 'failed': False, 'item': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}, '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}})
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0| Install external requirements with supported backends: |
galaxy: default. Lots of WIP in the air. |
gilt: nice overlays on top of git. |
shell: your own. |
--> Action: 'dependency'
- downloading role 'repo-remi', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-repo-remi/archive/1.2.0.tar.gz
- extracting geerlingguy.repo-remi to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.repo-remi
- geerlingguy.repo-remi (1.2.0) was installed successfully
- downloading role 'apache', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-apache/archive/3.0.3.tar.gz
- extracting geerlingguy.apache to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.apache
- geerlingguy.apache (3.0.3) was installed successfully
- downloading role 'mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-mysql/archive/2.9.4.tar.gz
- extracting geerlingguy.mysql to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.mysql
- geerlingguy.mysql (2.9.4) was installed successfully
- downloading role 'php-versions', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php-versions/archive/3.0.0.tar.gz
- extracting geerlingguy.php-versions to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php-versions
- geerlingguy.php-versions (3.0.0) was installed successfully
- downloading role 'php', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php/archive/3.7.0.tar.gz
- extracting geerlingguy.php to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php
- geerlingguy.php (3.7.0) was installed successfully
- downloading role 'php-mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php-mysql/archive/2.0.2.tar.gz
- extracting geerlingguy.php-mysql to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php-mysql
- geerlingguy.php-mysql (2.0.2) was installed successfullyansible-playbook --syntax-check |
| Complementary to lint, but need the external roles to work. |
molecule syntax
--> Scenario: 'default'
--> Action: 'syntax'
playbook: molecule/default/playbook.yml| Create the temporary platforms. |
| Local backends are faster and more reliable. |
Cloud backends: abuse of retry, be patient |
--> Scenario: 'default'
--> Action: 'create'
PLAY [Create] ******************************************************************
TASK [Log into a Docker registry] **********************************************
skipping: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Create Dockerfiles from image names] *************************************
skipping: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Discover local Docker images] ********************************************
ok: [localhost] => (item={'changed': False, 'skipped': True, 'skip_reason': 'Conditional result was False', '_ansible_no_log': False, 'item': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}, '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}})
TASK [Build an Ansible compatible image] ***************************************
skipping: [localhost] => (item={'changed': False, 'skipped': True, 'skip_reason': 'Conditional result was False', '_ansible_no_log': False, 'item': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}, '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}})
TASK [Create docker network(s)] ************************************************
TASK [Determine the CMD directives] ********************************************
skipping: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Create molecule instance(s)] *********************************************
changed: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Wait for instance(s) creation to complete] *******************************
FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
changed: [localhost] => (item={'started': 1, 'finished': 0, 'ansible_job_id': '57155955616.7787', 'results_file': '/home/fab/.ansible_async/57155955616.7787', '_ansible_parsed': True, 'changed': True, '_ansible_no_log': False, 'failed': False, 'item': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}, '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}})
PLAY RECAP *********************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0| Optional actions which bring the system to a given state prior to converge. |
| remote database connections |
| user accounts |
| kubeconfig |
--> Scenario: 'default'
--> Action: 'prepare'
PLAY [Prepare] *****************************************************************
TASK [delete the kubeconfig if present] ****************************************
ok: [kind-default -> localhost]
TASK [Fetch the kubeconfig] ****************************************************
changed: [kind-default]
TASK [Change the kubeconfig port to the proper value] **************************
changed: [kind-default -> localhost]
TASK [Wait for the Kubernetes API to become available (this could take a minute)] ***
FAILED - RETRYING: Wait for the Kubernetes API to become available (this could take a minute) (60 retries left).
FAILED - RETRYING: Wait for the Kubernetes API to become available (this could take a minute) (59 retries left).
FAILED - RETRYING: Wait for the Kubernetes API to become available (this could take a minute) (58 retries left).
FAILED - RETRYING: Wait for the Kubernetes API to become available (this could take a minute) (57 retries left).
FAILED - RETRYING: Wait for the Kubernetes API to become available (this could take a minute) (56 retries left).
ok: [kind-default]
PLAY RECAP *********************************************************************
kind-default : ok=4 changed=2 unreachable=0 failed=0| Execute the main role. |
--> Scenario: 'default'
--> Action: 'converge'
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
PLAY [Verify] ******************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Get all pods in osdk-test] ***********************************************
ok: [localhost]
TASK [Output pods] *************************************************************
ok: [localhost] => {
"pods": {
"changed": false,
"failed": false,
"resources": []
}
}
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.| The main role is executed again; the result should change nothing in order to achieve idempotence. |
| Key feature! |
| Idempotence is a goal difficult to achieve, particularly on Windows. |
--> Action: 'idempotence'
Idempotence completed successfully.| Optional actions which are not in the role, after converge. |
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side_effect playbook not configured.| Execute an audit tool to verify that the final state is meeting expectations. |
| testinfra - default, python based. |
| Goss - Linux only. Easy and fast. |
| Inspec - ruby DSL, works well for Windows targets. |
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/fab/src/themr0c/talk-jdl2019/examples/geerlingguy.phpmyadmin/molecule/default/tests/...
============================= test session starts ==============================
platform linux2 -- Python 2.7.16, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/fab/src/themr0c/talk-jdl2019/examples/geerlingguy.phpmyadmin/molecule/default, inifile:
plugins: testinfra-1.16.0
collected 1 item
tests/test_default.py . [100%]
========================== 1 passed in 12.76 seconds ===========================
Verifier completed successfully.| Destroy the temporary platforms. |
molecule destroy
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item={'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True})
TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
changed: [localhost] => (item={'started': 1, 'finished': 0, 'ansible_job_id': '498157453262.13184', 'results_file': '/home/fab/.ansible_async/498157453262.13184', '_ansible_parsed': True, 'changed': True, '_ansible_no_log': False, 'failed': False, 'item': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}, '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': {'name': 'kind-default', 'groups': ['k8s'], 'image': 'bsycorp/kind:latest-1.12', 'privileged': True, 'override_command': False, 'exposed_ports': ['8443/tcp', '10080/tcp'], 'published_ports': ['0.0.0.0:9443:8443/tcp'], 'pre_build_image': True}})
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0Objective: validate that all roles are in a good shape, ready to deliver.
Test one role with molecule
Test multiple roles with tox
Automate on Continuous Integration platform
Before committing any changes to Git (tradeoffs: blocking, slow, antivirus).
Execute all tests on the named role ${rolename}:
cd ${rolename}
molecule test --allOrchestrate tests on a collection of roles.
Isolated python virtual environments.
tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software.
pip install tox virtualenvtox.iniMolecule role == Tox named environment
Running platforms are not isolated!
[tox]
envlist =
my_example_role
another_role
skipsdist = true
[testenv]
basepython = python3
commands = bash -c "(cd {toxinidir}/roles/{envname} && molecule --debug
test --all)"
description = molecule test role {envname}
deps = -r {toxinidir}/requirements.txt
setenv = MOLECULE_EPHEMERAL_DIRECTORY={envname}
sitepackages = true
whitelist_externals =
/bin/bash
/usr/bin/rubocoptox (1)
tox -e ${rolename} (2)
| 1 | Execute all molecule tests on all roles |
| 2 | Same, limited to the named role ${rolename} |
On pull request. Never merge broken code!
At commit on release branches / tags.
Foresee long compute time !
Need to run privileged Docker containers
docker_image_name="quay.io/ansible/molecule:2.19" (1)
gid_map="$(grep docker /etc/group |cut -d: -f3,3)" (2)
local_playbooks_absolute_path="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")" (3)
docker run \
-e "WORKDIR=${molecule_workdir}" \ (3)
-v /var/run/docker.sock:/var/run/docker.sock \ (4)
-v "${local_playbooks_absolute_path}:${molecule_workdir}" (3)
"${docker_image_name}" bash "molecule test"
| 1 | Docker image from https://quay.io/repository/ansible/molecule |
| 2 | Map local user |
| 3 | Mount the code |
| 4 | Share docker socket |
| 1 repository == 1 role |
| multiple roles ⇒ external orchestration |
scenarios ⇒ sequential
platforms ⇒ parallel
Galaxy role example: geerlingguy.phpmyadmin
Initialize our own role
molecule in Kubernetes Operator SDK
sudo aptitude install python3 python3-dev python3-psutil
mkvirtualenv -p /usr/bin/python3 molecule-python3
pip install molecule docker
git clone git@github.com:geerlingguy/ansible-role-phpmyadmin.git geerlingguy.phpmyadmin
cd geerlingguy.phpmyadmin
molecule test
Usage: molecule init [OPTIONS] COMMAND [ARGS]...
Initialize a new role or scenario.
Options:
--help Show this message and exit.
Commands:
role Initialize a new role for use with Molecule.
scenario Initialize a new scenario for use with...
template Initialize a new role from a Cookiecutter...molecule init role --driver-name docker --verifier-name goss --role-name jdl
--> Initializing new role jdl...
Initialized role in /home/fab/src/jdl-2019/jdl successfully.--> Executing Ansible Lint on /home/fab/src/jdl-2019/jdl/molecule/default/playbook.yml...
[701] Role info should contain platforms
/home/fab/src/jdl-2019/jdl/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}}
[703] Should change default metadata: author
/home/fab/src/jdl-2019/jdl/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}}
[703] Should change default metadata: description
/home/fab/src/jdl-2019/jdl/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}}
[703] Should change default metadata: company
/home/fab/src/jdl-2019/jdl/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}}
[703] Should change default metadata: license
/home/fab/src/jdl-2019/jdl/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/fab/src/jdl-2019/jdl/meta/main.yml'}}molecule test
--> Validating schema /home/fab/src/jdl-2019/jdl/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix
└── default
├── lint
├── cleanup
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
├── cleanup
└── destroy
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/fab/src/jdl-2019/jdl/...
Lint completed successfully.
--> Executing Yamllint on files found in /home/fab/src/jdl-2019/jdl/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/fab/src/jdl-2019/jdl/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'
playbook: /home/fab/src/jdl-2019/jdl/molecule/default/playbook.yml
--> Scenario: 'default'
--> Action: 'create'
PLAY [Create] ******************************************************************
TASK [Log into a Docker registry] **********************************************
skipping: [localhost] => (item=None)
TASK [Create Dockerfiles from image names] *************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Discover local Docker images] ********************************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Build an Ansible compatible image] ***************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Create docker network(s)] ************************************************
TASK [Determine the CMD directives] ********************************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Create molecule instance(s)] *********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) creation to complete] *******************************
FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
changed: [localhost] => (item=None)
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=4 unreachable=0 failed=0
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [instance]
PLAY RECAP *********************************************************************
instance : ok=1 changed=0 unreachable=0 failed=0
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Goss tests found in /home/fab/src/jdl-2019/jdl/molecule/default/tests/...
PLAY [Verify] ******************************************************************
TASK [Gathering Facts] *********************************************************
ok: [instance]
TASK [Download and install Goss] ***********************************************
changed: [instance]
TASK [Copy Goss tests to remote] ***********************************************
changed: [instance] => (item=/home/fab/src/jdl-2019/jdl/molecule/default/tests/test_default.yml)
TASK [Register test files] *****************************************************
changed: [instance]
TASK [Execute Goss tests] ******************************************************
changed: [instance] => (item=/tmp/test_default.yml)
TASK [Display details about the Goss results] **********************************
ok: [instance] => (item={'changed': True, 'end': '2019-03-20 22:08:15.317017', 'stdout': 'File: /etc/hosts: exists: matches expectation: [true]\nFile: /etc/hosts: owner: matches expectation: ["root"]\nFile: /etc/hosts: group: matches expectation: ["root"]\n\n\nTotal Duration: 0.000s\nCount: 3, Failed: 0, Skipped: 0', 'cmd': ['/usr/local/bin/goss', '-g', '/tmp/test_default.yml', 'validate', '--format', 'documentation'], 'rc': 0, 'start': '2019-03-20 22:08:14.710863', 'stderr': '', 'delta': '0:00:00.606154', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': False, '_raw_params': '/usr/local/bin/goss -g /tmp/test_default.yml validate --format documentation', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['File: /etc/hosts: exists: matches expectation: [true]', 'File: /etc/hosts: owner: matches expectation: ["root"]', 'File: /etc/hosts: group: matches expectation: ["root"]', '', '', 'Total Duration: 0.000s', 'Count: 3, Failed: 0, Skipped: 0'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': '/tmp/test_default.yml', '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': '/tmp/test_default.yml'}) => {
"msg": [
"File: /etc/hosts: exists: matches expectation: [true]",
"File: /etc/hosts: owner: matches expectation: [\"root\"]",
"File: /etc/hosts: group: matches expectation: [\"root\"]",
"",
"",
"Total Duration: 0.000s",
"Count: 3, Failed: 0, Skipped: 0"
]
}
TASK [Fail when tests fail] ****************************************************
skipping: [instance] => (item={'changed': True, 'end': '2019-03-20 22:08:15.317017', 'stdout': 'File: /etc/hosts: exists: matches expectation: [true]\nFile: /etc/hosts: owner: matches expectation: ["root"]\nFile: /etc/hosts: group: matches expectation: ["root"]\n\n\nTotal Duration: 0.000s\nCount: 3, Failed: 0, Skipped: 0', 'cmd': ['/usr/local/bin/goss', '-g', '/tmp/test_default.yml', 'validate', '--format', 'documentation'], 'rc': 0, 'start': '2019-03-20 22:08:14.710863', 'stderr': '', 'delta': '0:00:00.606154', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': False, '_raw_params': '/usr/local/bin/goss -g /tmp/test_default.yml validate --format documentation', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['File: /etc/hosts: exists: matches expectation: [true]', 'File: /etc/hosts: owner: matches expectation: ["root"]', 'File: /etc/hosts: group: matches expectation: ["root"]', '', '', 'Total Duration: 0.000s', 'Count: 3, Failed: 0, Skipped: 0'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': '/tmp/test_default.yml', '_ansible_item_result': True, '_ansible_ignore_errors': None, '_ansible_item_label': '/tmp/test_default.yml'})
PLAY RECAP *********************************************************************
instance : ok=6 changed=4 unreachable=0 failed=0
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0| building a simple memcached-operator powered by Ansible using tools and libraries provided by the Operator SDK |
Enables developers to build Operators based on their expertise without requiring knowledge of Kubernetes API complexities.
export GOPATH=$HOME/.go
mkdir -p $GOPATH/src/github.com/operator-framework
cd $GOPATH/src/github.com/operator-framework
git clone https://github.com/operator-framework/operator-sdk
cd operator-sdk
git checkout master
make dep
make install
mkvirtualenv -p /usr/bin/python3 operator-sdk
pip install docker molecule openshiftoperator-sdk new memcached-operator --api-version=cache.example.com/v1alpha1 --kind=Memcached --type=ansible
cd memcached-operator
molecule testansible-lint and molecule are great tools. They’ve been built and tested by the community that we see as essential parts of enhancing development of Ansible automation. By adopting these tools, Red Hat intends to invest resources working with the community to make them even better.
pip install molecule==2.20.0
docker pull quay.io/ansible/molecule:2.20 (1)
| 1 | Use named tags |
| latest == master == unstable |
No longer requires sudo. |
| No longer specifies USER molecule. |
| and more … |
| ansible-lint >=4.0.2,<5 |
Major release of ansible-lint |
| New backend: Linode |
| Customise the location of the Dockerfile.j2 |
New options for docker: purge_networks, pid_mode, buildargs, override_command |
| Molecule can now be called as a Python module. |
python -m molecule| Lots of improvements. |
| New ‘Getting Started’ guide. |
| Example for using systemd enabled Docker images |
We want molecule to become the defacto standard tool, or sdk if you will, for content creators.